import org.junit.jupiter.api.Test;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalQuery;


public class LocalDateDemo {
    /**
     * MAX：localDate对象能表示日期的最大值。
     * MIN：localDate对象能表示日期的最小值。
     */
    @Test
    public void test01(){
        System.out.println(LocalDate.MAX); // 输出 +999999999-12-31
        System.out.println(LocalDate.MIN); // 输出 -999999999-01-01
    }
}

class LocalDateDemo01 {

    /**
     * from(TemporalAccessor temporal)
     * 作用：从其他包含年月日数据的时间类获取localDate对象。
     */
    @Test
    public void test01(){
        LocalDateTime dateTime01 = LocalDateTime.parse("2023-01-01T12:00:00");
        System.out.println(LocalDate.from(dateTime01)); // 输出 2023-01-01
    }

    /**
     * now()
     * 作用：从操作系统获取日期数据。
     *
     * now(ZoneId zone)
     * 作用：获取其他时区此刻的日期。
     *
     * now(Clock clock)
     * 作用：从clock对象获取日期数据。
     */
    @Test
    public void test02() {
        System.out.println(LocalDate.now()); // 输出 2023-05-04

        ZoneId zoneId01 = ZoneId.of("America/Los_Angeles");
        System.out.println(LocalDate.now(zoneId01)); // 输出 2023-05-04

        Instant instant01 = Instant.parse("2023-01-02T06:00:00Z");
        Clock fixedClock01 = Clock.fixed(instant01, zoneId01);
        System.out.println(LocalDate.now(fixedClock01)); // 输出 2023-01-01
    }

    /**
     * of(int year, int month, int dayOfMonth)
     * of(int year, Month month, int dayOfMonth)
     * 作用：给定年月日数据构建localDate对象。
     */
    @Test
    public void test03() {
        System.out.println(LocalDate.of(2023, 1, 1)); // 输出 2023-01-01
        System.out.println(LocalDate.of(2023, Month.JANUARY, 1)); // 输出 2023-01-01
    }

    /**
     * ofEpochDay(long epochDay)
     * 作用：给定距离EPOCH日期的天数构建localDate对象。
     *
     * ofYearDay(int year, int dayOfYear)
     * 作用：给定年份和天数构建localDate对象。
     */
    @Test
    public void test04() {
        System.out.println(LocalDate.ofEpochDay(0)); // 输出 1970-01-01
        System.out.println(LocalDate.ofEpochDay(1)); // 输出 1970-01-02

        System.out.println(LocalDate.ofYearDay(2023, 1)); // 输出 2023-01-01
        System.out.println(LocalDate.ofYearDay(2023, Month.OCTOBER.firstDayOfYear(false))); // 输出 2023-10-01
    }

    /**
     * parse(CharSequence text)
     * parse(CharSequence text, DateTimeFormatter formatter)
     * 作用：将字符串解析成localDate对象，字符串默认格式yyyy-MM-dd，formatter用来指定字符串的格式。
     */
    @Test
    public void test05() {
        System.out.println(LocalDate.parse("2023-01-01")); // 输出 2023-01-01

        DateTimeFormatter df01 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        System.out.println(LocalDate.parse("2023/01/01", df01)); // 输出 2023-01-01
    }
}

class LocalDateDemo02 {

    /**
     * atStartOfDay()
     * atStartOfDay(ZoneId zone)
     * 作用：给localDate对象添加00:00时间数据使其生成localDateTime对象，
     *      若再添加zone时区参数则生成含有时区概念的zonedDateTime对象。
     */
    @Test
    public void test01() {
        LocalDate date01 = LocalDate.parse("2023-01-01");
        LocalDateTime dateTime01 = date01.atStartOfDay();
        System.out.println(dateTime01); // 输出 2023-01-01T00:00

        ZoneId zoneId01 = ZoneId.of("Asia/Shanghai");
        ZonedDateTime zonedDateTime01 = date01.atStartOfDay(zoneId01);
        System.out.println(zonedDateTime01); // 输出 2023-01-01T00:00+08:00[Asia/Shanghai]
    }

    /**
     * atTime(int hour, int minute)
     * atTime(int hour, int minute, int second)
     * atTime(int hour, int minute, int second, int nanoOfSecond)
     * atTime(LocalTime time)
     * atTime(OffsetTime time)
     * 作用：给定时分秒数据生成localDateTime对象。
     */
    @Test
    public void test02() {
        LocalDate date01 = LocalDate.parse("2023-01-01");
        System.out.println(date01.atTime(9, 0, 0)); // 输出 2023-01-01T09:00

        LocalTime time01 = LocalTime.parse("08:00:00");
        System.out.println(date01.atTime(time01)); // 输出 2023-01-01T08:00
    }

    /**
     * compareTo(ChronoLocalDate other)
     * isAfter(ChronoLocalDate other)
     * isBefore(ChronoLocalDate other)
     * isEqual(ChronoLocalDate other)
     * 作用：比较两个localDate对象大小，other为null时报NPE异常。
     */
    @Test
    public void test03() {
        LocalDate date01 = LocalDate.of(2023, 1, 1);
        LocalDate date02 = LocalDate.of(2023, 1, 1);
        LocalDate date03 = LocalDate.of(2023, 1, 2);
        LocalDate date04 = null;
        System.out.println(date01.compareTo(date02)); // 输出 0（相等）
        System.out.println(date01.compareTo(date03)); // 输出 -1（date01小于date03）
        System.out.println(date01.compareTo(date04)); // NPE异常

        System.out.println(date01.isEqual(date02)); // 输出 true
        System.out.println(date01.isBefore(date03)); // 输出 true
        System.out.println(date01.isBefore(date04)); // NPE异常
    }

    /**
     * format(DateTimeFormatter formatter)
     * 作用：localDate对象根据指定的格式输出字符串。
     */
    @Test
    public void test04() {
        LocalDate date01 = LocalDate.of(2023, 1, 1);
        DateTimeFormatter df01 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter df02 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        DateTimeFormatter df03 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");

        System.out.println(date01.format(df01)); // 输出 2023-01-01
        System.out.println(date01.format(df02)); // 输出 2023/01/01
        System.out.println(date01.format(df03)); // 输出 2023年01月01日
    }

    /**
     * isSupported(TemporalField field)
     * 作用：判断对象是否支持指定的时间分量。
     *
     * isSupported(TemporalUnit unit)
     * 作用：判断对象是否支持指定的时间单位。时间单位有年、月、日、时、分、秒、毫秒、纳秒、微妙等等。
     */
    @Test
    public void test05() {
        LocalDate date01 = LocalDate.of(2023, 2, 1);

        System.out.println(date01.isSupported(ChronoField.YEAR)); // 输出 true
        System.out.println(date01.isSupported(ChronoField.HOUR_OF_DAY)); // 输出 false

        System.out.println(date01.isSupported(ChronoUnit.YEARS)); // 输出 true
        System.out.println(date01.isSupported(ChronoUnit.HOURS)); // 输出 false
    }

    /**
     * get(TemporalField field)
     * getLong(TemporalField field)
     * 作用：获取指定的时间分量，如年、月、日、时、分、秒、星期、一年中的第几天等等，详细可查看ChronoField类。
     *      但不是所有时间分量都能获取。如localDate对象无法获取时、分、秒等时间分量。
     *
     * getDayOfMonth()
     * getDayOfWeek()
     * getDayOfYear()
     * getEra()
     * getMonth()
     * getMonthValue()
     * getYear()
     * 作用：作用类似get(TemporalField field)。
     */
    @Test
    public void test06() {
        LocalDate date01 = LocalDate.of(2023, 2, 1);
        System.out.println(date01.get(ChronoField.YEAR)); // 输出 2023
        System.out.println(date01.get(ChronoField.MONTH_OF_YEAR)); // 输出 2
        System.out.println(date01.get(ChronoField.DAY_OF_MONTH)); // 输出 1
        System.out.println(date01.get(ChronoField.DAY_OF_WEEK)); // 输出 3
        System.out.println(date01.get(ChronoField.DAY_OF_YEAR)); // 输出 32

        System.out.println(date01.getYear()); // 输出 2023
        System.out.println(date01.getMonthValue()); // 输出 2
        System.out.println(date01.getDayOfMonth()); // 输出 1
        System.out.println(date01.getDayOfWeek()); // 输出 WEDNESDAY
        System.out.println(date01.getDayOfYear()); // 输出 32
    }

    /**
     * isLeapYear()
     * 作用：判断是否是闰年
     */
    @Test
    public void test07() {
        LocalDate date01 = LocalDate.of(2000, 1, 1);
        LocalDate date02 = LocalDate.of(2023, 1, 1);
        System.out.println(date01.isLeapYear()); // 输出 true
        System.out.println(date02.isLeapYear()); // 输出 false
    }

    /**
     * lengthOfMonth()
     * 作用：获取localDate对象的月份的天数
     *
     * lengthOfYear()
     * 作用：获取localDate对象的年份的天数
     */
    @Test
    public void test08() {
        LocalDate date01 = LocalDate.of(2000, 2, 1);
        LocalDate date02 = LocalDate.of(2023, 2, 1);
        System.out.println(date01.lengthOfMonth()); // 输出 29
        System.out.println(date02.lengthOfMonth()); // 输出 28

        System.out.println(date01.lengthOfYear()); // 输出 366
        System.out.println(date02.lengthOfYear()); // 输出 365
    }

    /**
     * minus(long amountToSubtract, TemporalUnit unit)
     * minus(TemporalAmount amountToSubtract)
     * minusDays(long daysToSubtract)
     * minusMonths(long monthsToSubtract)
     * minusWeeks(long weeksToSubtract)
     * minusYears(long yearsToSubtract)
     * 作用：在指定的时间单位上减去固定值。
     */
    @Test
    public void test09() {
        LocalDate date01 = LocalDate.of(2023, 10, 10);
        System.out.println(date01.minus(1, ChronoUnit.YEARS)); // 输出 2022-10-10
        System.out.println(date01.minus(1, ChronoUnit.MONTHS)); // 输出 2023-09-10
        System.out.println(date01.minus(1, ChronoUnit.DAYS)); // 输出 2023-10-09

        Period period01 = Period.of(1, 1, 1);
        System.out.println(date01.minus(period01)); // 输出 2022-09-09

        System.out.println(date01.minusYears(1)); // 输出 2022-10-10
        System.out.println(date01.minusMonths(1)); // 输出 2023-09-10
        System.out.println(date01.minusDays(1)); // 输出 2023-10-09
        System.out.println(date01.minusWeeks(1)); // 输出 2023-10-03
    }

    /**
     * plus(long amountToSubtract, TemporalUnit unit)
     * plus(TemporalAmount amountToSubtract)
     * plusDays(long daysToSubtract)
     * plusMonths(long monthsToSubtract)
     * plusWeeks(long weeksToSubtract)
     * plusYears(long yearsToSubtract)
     * 作用：在指定的时间单位上加上固定值。
     */
    @Test
    public void test10() {
        LocalDate date01 = LocalDate.of(2023, 10, 10);
        System.out.println(date01.plus(1, ChronoUnit.YEARS)); // 输出 2024-10-10
        System.out.println(date01.plus(1, ChronoUnit.MONTHS)); // 输出 2023-11-10
        System.out.println(date01.plus(1, ChronoUnit.DAYS)); // 输出 2023-10-11

        Period period01 = Period.of(1, 1, 1);
        System.out.println(date01.plus(period01)); // 输出 2024-11-11

        System.out.println(date01.plusYears(1)); // 输出 2024-10-10
        System.out.println(date01.plusMonths(1)); // 输出 2023-11-10
        System.out.println(date01.plusDays(1)); // 输出 2023-10-11
        System.out.println(date01.plusWeeks(1)); // 输出 2023-10-17
    }

    /**
     * query(TemporalQuery<R> query)
     * 作用：TemporalQuery是一个函数接口，有一个queryFrom(TemporalAccessor temporal)方法。
     *      其中的temporal对象就是调用query()方法的对象。通过实现TemporalQuery接口来自定义查询日期的逻辑，
     *      比如我有个需求需要经常查询某个日期所在的季节，或者某个日期距离下一个闰年还有多少天等等。官方没有提供这样的
     *      查询逻辑而我们又经常需要用到，此时我们可以实现TemporalQuery接口来自定义查询逻辑。
     *      TemporalQueries类已经为我们定义了常用的查询逻辑，具体细节可查看源码。
     */
    @Test
    public void test11() {
        LocalDate date01 = LocalDate.of(2023, 1, 1);
        TemporalQuery<String> querySeason = data -> {
            if (data.isSupported(ChronoField.MONTH_OF_YEAR) == false) {
                return null;
            }
            int month = data.get(ChronoField.MONTH_OF_YEAR);
            switch (month) {
                case 12:
                case 1:
                case 2:
                    return "Winter";
                case 3:
                case 4:
                case 5:
                    return "Spring";
                case 6:
                case 7:
                case 8:
                    return "Summer";
                case 9:
                case 10:
                case 11:
                    return "Autumn";
                default:
                    return "Unexpected value: " + month;
            }
        };

        System.out.println(date01.query(querySeason)); // 输出 Winter
    }

    /**
     * range(TemporalField field)
     * 作用：获取localDate对象相关时间分量的范围。
     */
    @Test
    public void test12() {
        LocalDate date01 = LocalDate.of(2023, 1, 1);
        System.out.println(date01.range(ChronoField.MONTH_OF_YEAR)); // 输出 1 - 12
        System.out.println(date01.range(ChronoField.DAY_OF_MONTH)); // 输出 1 - 31
        System.out.println(date01.range(ChronoField.DAY_OF_WEEK)); // 输出 1 - 7
    }

    /**
     * toEpochDay()
     * 作用：获取localDate对象距离EPOCH日期的天数，如果在EPOCH日期之前则为负数。
     */
    @Test
    public void test13() {
        LocalDate date01 = LocalDate.of(1970, 1, 1);
        LocalDate date02 = LocalDate.of(1970, 1, 2);
        LocalDate date03 = LocalDate.of(1969, 12, 31);
        System.out.println(date01.toEpochDay()); // 输出 0
        System.out.println(date02.toEpochDay()); // 输出 1
        System.out.println(date03.toEpochDay()); // 输出 -1
    }

    /**
     * until(ChronoLocalDate endDateExclusive)
     * 作用：获取两个日期之间的时间长度，返回Period对象
     *
     * until(Temporal endExclusive, TemporalUnit unit)
     * 作用：根据时间单位计算一个日期距离另一个日期的值。
     */
    @Test
    public void test14() {
        LocalDate date01 = LocalDate.of(2022, 6, 2);
        LocalDate date02 = LocalDate.of(2023, 10, 10);
        System.out.println(date01.until(date02)); // 输出 P1Y4M8D
        System.out.println(date01.until(date02, ChronoUnit.YEARS)); // 输出 1
        System.out.println(date01.until(date02, ChronoUnit.MONTHS)); // 输出 16
        System.out.println(date01.until(date02, ChronoUnit.DAYS)); // 输出 495
    }

    /**
     * with(TemporalField field, long newValue)
     * withDayOfMonth(int dayOfMonth)
     * withDayOfYear(int dayOfYear)
     * withMonth(int month)
     * withYear(int year)
     * 作用：调整localDate对象相关时间分量的值。
     *
     * with(TemporalAdjuster adjuster)
     * 作用：该方法跟query(TemporalQuery<R> query)类似，query()方法是根据给定的查询逻辑查
     *      询localDate对象。而with()方法是根据给定的修改逻辑修改localDate对象。TemporalAdjuster
     *      也是一个函数接口，包含Temporal adjustInto(Temporal temporal)方法。其中temporal对象
     *      就是调用with()方法的对象，通过实现TemporalAdjuster接口实现对localDate对象的修改逻辑。
     */
    @Test
    public void test15() {
        LocalDate date01 = LocalDate.of(2023, 10, 10);
        System.out.println(date01.with(ChronoField.YEAR, 2022)); // 输出 2022-10-10
        System.out.println(date01.with(ChronoField.MONTH_OF_YEAR, 2)); // 输出 2023-02-10

        System.out.println(date01.withYear(2022)); // 输出 2022-10-10
        System.out.println(date01.withMonth(2)); // 输出 2023-02-10

        /** 把日期对象yyyy-MM-dd 修改为 yyyy-MM-01 **/
        TemporalAdjuster adjusterDayOfMonthTo1 = date -> {
            if (date.isSupported(ChronoUnit.DAYS)) {
                return date.with(ChronoField.DAY_OF_MONTH, 1);
            }
            return null;
        };
        System.out.println(date01.with(adjusterDayOfMonthTo1)); // 输出 2023-10-01
    }
}
